using System;
using System.Collections.Generic;
using System.Text;
using BodySoundSimulator.CaseData;
using BodySoundSimulator.AbsoluteMapData;
using NAudio.Wave;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace BodySoundSimulator.CommonElements
{
	class TestingAuscultationSitePlayHelper
	{
		#region fields
		CaseInformation caseInfo;
		AuscultationConfiguration configuration;
		PatientView view;

		List<TestingAuscultationSiteInfo> testingAuscultationSiteInfoList = new List<TestingAuscultationSiteInfo>();
		DateTime startTime;
		System.Windows.Forms.Timer timerWatch = new System.Windows.Forms.Timer();
		#endregion

		#region Properties

		public CaseInformation CaseInfo
		{
			get { return caseInfo; }
			set { caseInfo = value; }
		}

		public AuscultationConfiguration Configuration
		{
			get { return configuration; }
			set { configuration = value; }
		}

		public PatientView View
		{
			get { return view; }
			set { view = value; }
		}
		#endregion

		#region Constructors
		public TestingAuscultationSitePlayHelper(CaseInformation caseInfo, AuscultationConfiguration configuration, PatientView view)
		{
			this.caseInfo = caseInfo;
			this.configuration = configuration;
			this.view = view;
			this.timerWatch.Tick += new EventHandler(timerWatch_Tick);
		}

		#endregion

		#region Public methods
		public void LoadSiteInfoList()
		{
			this.testingAuscultationSiteInfoList.Clear();
			switch (this.view)
			{
				case PatientView.FrontView:
					for (int index = 0; index < this.caseInfo.FrontViewAuscultationSites.Count; index++)
					{
						AuscultationSiteInfo siteInfo = this.caseInfo.FrontViewAuscultationSites[index];
						string soundsFilePath = SimulatorFileManager.GetSoundFilePath(this.caseInfo.CaseID,
							siteInfo,
							PatientView.FrontView,
							this.configuration);
						TestingAuscultationSiteInfo testingSiteInfo = new TestingAuscultationSiteInfo(soundsFilePath, (FrontViewCommonAuscultationSiteType)index);
						this.testingAuscultationSiteInfoList.Add(testingSiteInfo);
					}
					break;
				case PatientView.BackView:
					for (int index = 0; index < this.caseInfo.BackViewAuscultationSites.Count; index++)
					{
						AuscultationSiteInfo siteInfo = this.caseInfo.BackViewAuscultationSites[index];
						string soundsFilePath = SimulatorFileManager.GetSoundFilePath(this.caseInfo.CaseID,
							siteInfo,
							PatientView.BackView,
							this.configuration);
						TestingAuscultationSiteInfo testingSiteInfo = new TestingAuscultationSiteInfo(soundsFilePath, (BackViewCommonAuscultationSiteType)index);
						this.testingAuscultationSiteInfoList.Add(testingSiteInfo);
					}
					break;
			}
		}

		public void StartPlaying(SoundMapCellInfo selectedCellInfo)
		{
			if (selectedCellInfo != null)
			{
				int cellID = selectedCellInfo.CellID;
				foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
				{
					//TODO: get volume factor based on the given location
					float volume = CalculateVolume(cellID, siteInfo, this.view);
					if (volume != 0)
					{
						siteInfo.VolumeStream.Volume = volume;
						TimeSpan span = DateTime.Now - startTime;
						SetCurrentTime(siteInfo.MainOutputStream, span);
						Debug.WriteLine("start playing" + cellID.ToString());
						siteInfo.WaveOut.Play();
					}
				}
				this.timerWatch.Enabled = true;
			}
		}

		public void StartPlaying(int cellID)
		{
			foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
			{
				//TODO: get volume factor based on the given location
				float volume = CalculateVolume(cellID, siteInfo, this.view);
				if (volume != 0)
				{
					siteInfo.VolumeStream.Volume = volume;
					TimeSpan span = DateTime.Now - startTime;
					SetCurrentTime(siteInfo.MainOutputStream, span);
					Debug.WriteLine("START playing" + siteInfo.CommonAuscultationSiteType.ToString());
					siteInfo.WaveOut.Play();
				}
			}
			this.timerWatch.Enabled = true;
		}

		public void LoadWaveOuts()
		{
			try
			{
				CreateWaveOuts();
			}
			catch (Exception driverCreateException)
			{
				MessageBox.Show(String.Format("{0}", driverCreateException.Message));
				return;
			}

			foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
			{
				siteInfo.VolumeStream = CreateInputStream(siteInfo.FileName);
				siteInfo.MainOutputStream = CreateMeteringStream(siteInfo.VolumeStream);
				try
				{
					siteInfo.WaveOut.Init(siteInfo.MainOutputStream);
				}
				catch (Exception initException)
				{
					MessageBox.Show(String.Format("{0}", initException.Message), "Error Initializing Output");
					return;
				}
			}
			this.startTime = DateTime.Now;
		}

		public void CloseWaveOuts()
		{
			foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
			{
				if (siteInfo.WaveOut != null)
				{
					siteInfo.WaveOut.Stop();
				}
				if (siteInfo.MainOutputStream != null)
				{
					siteInfo.VolumeStream.Close();
					siteInfo.VolumeStream = null;
					siteInfo.MainOutputStream.Close();
					siteInfo.MainOutputStream = null;
				}
				if (siteInfo.WaveOut != null)
				{
					siteInfo.WaveOut.Dispose();
					siteInfo.WaveOut = null;
				}
			}
		}

		public void StopPlaying()
		{
			foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
			{
				if (siteInfo.WaveOut != null)
				{
					Debug.WriteLine("Stop playing" + siteInfo.CommonAuscultationSiteType.ToString());
					siteInfo.WaveOut.Stop();
				}
			}
			this.timerWatch.Enabled = false;
		}

		#endregion

		#region Private methods

		private float CalculateVolume(int cellID,
			TestingAuscultationSiteInfo siteInfo,
			PatientView view)
		{
			switch (view)
			{
				case PatientView.FrontView:
					FrontViewCommonAuscultationSiteType frontViewSite = (FrontViewCommonAuscultationSiteType)siteInfo.CommonAuscultationSiteType;
					return this.caseInfo.SoundMap.FrontViewContributionMap[cellID].FrontViewEffects[(int)frontViewSite].Effect;
				case PatientView.BackView:
					BackViewCommonAuscultationSiteType backViewType = (BackViewCommonAuscultationSiteType)siteInfo.CommonAuscultationSiteType;
					return this.caseInfo.SoundMap.BackViewContributionMap[cellID].BackViewEffects[(int)backViewType].Effect;
				default:
					return 0;
			}
		}

		private void CreateWaveOuts()
		{
			CloseWaveOuts();
			foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
			{
				WaveCallbackInfo callbackInfo = WaveCallbackInfo.NewWindow();
				WaveOut outputDevice = new WaveOut(callbackInfo);
				siteInfo.WaveOut = outputDevice;
			}
		}

		private void SetCurrentTime(WaveStream waveStream, TimeSpan span)
		{
			if (waveStream.TotalTime.TotalMilliseconds <= span.TotalMilliseconds)
			{
				waveStream.CurrentTime = TimeSpan.FromMilliseconds(span.TotalMilliseconds % waveStream.TotalTime.TotalMilliseconds);
			}
			else
			{
				waveStream.CurrentTime = span;
			}
		}

		private WaveStream CreateMeteringStream(WaveChannel32 inputStream)
		{
			MeteringStream meteringStream = new MeteringStream(inputStream, inputStream.WaveFormat.SampleRate / 10);
			return meteringStream;
		}

		private static WaveChannel32 CreateInputStream(string fileName)
		{
			WaveChannel32 inputStream = null;
			if (fileName.EndsWith(".wav"))
			{
				WaveStream readerStream = new WaveFileReader(fileName);
				if (readerStream.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
				{
					readerStream = WaveFormatConversionStream.CreatePcmStream(readerStream);
					readerStream = new BlockAlignReductionStream(readerStream);
				}
				if (readerStream.WaveFormat.BitsPerSample != 16)
				{
					WaveFormat format = new WaveFormat(readerStream.WaveFormat.SampleRate,
						16, readerStream.WaveFormat.Channels);
					readerStream = new WaveFormatConversionStream(format, readerStream);
				}
				inputStream = new WaveChannel32(readerStream);
			}
			return inputStream;
		}

		void timerWatch_Tick(object sender, EventArgs e)
		{
			foreach (TestingAuscultationSiteInfo siteInfo in this.testingAuscultationSiteInfoList)
			{
				if (siteInfo.MainOutputStream != null)
				{
					if (siteInfo.MainOutputStream.Position >= siteInfo.MainOutputStream.Length)
					{
						siteInfo.MainOutputStream.Position = 0;
					}
				}
			}
		}
		#endregion
	}
}
